/*
 * A quick and dirty implementation of the meta object compiler.
 *
 * Worst limitations:
 *  - Signals and Slots cannot have arguments;
 *  - Parsing is done on lines, not on tokens;
 *  - Comments are not handled properly;
 *  - Matching is primitive, so certain styles of indentation may fail.
 */

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_LINE_SIZE		1024
#define MAX_CLASS_NAME		64
#define MAX_SIGNAL_NAME		64
#define	MAX_SIGNALS_NO		36
#define MAX_SLOT_NAME		64
#define	MAX_SLOTS_NO		36

/* Current line number. */
int lineno;

/* Current class name. */
char classname[MAX_CLASS_NAME];
/* Registered signals. */
char signals[MAX_SIGNAL_NAME][MAX_SIGNALS_NO];
/* Registered slots. */
char slots[MAX_SIGNAL_NAME][MAX_SIGNALS_NO];
/* Number of registered signals. */
int signals_no;
/* Number of registered slots. */
int slots_no;

/* Removes whitespaces at the beginning and the end of the string */
void eat_spaces(char *buf)
{
	char *p = buf;
	while (isspace(*p)) // remove prefix whitespace
		++p;
	if (p > buf)
		memmove(buf, p, strlen(p) + 1);
	p = buf;
	while (*p != '\0') // skip to end of string
		++p;
	while (p > buf && isspace(*(p - 1))) { // remove trailing whitespace
		--p;
		*p = '\0';
	}
}

/* Extracts "QPushButton" from the string "class QPushButton : public QWidget {" */
char *make_classname(char *buf)
{
	char *p = buf + 5; // "class" already matched
	while (isspace(*p)) // skip whitespace
		++p;
	buf = p;
	while (isalnum(*p) || *p == '_')
		++p;
	*p = '\0';
	return buf;
}

/* Extracts "clicked()" from the string "void clicked();" */
char *make_signal(char *buf)
{
	char *p = buf;
	while (*p != 0 && *p != '(')
		++p;
	if (*p == '\0')
		return NULL;
	--p;
	while (p > buf && (isalnum(*p) || *p == '_'))
		--p;
	buf = p + 1;
	while (*p != '\0')
		++p;
	if (*(p - 1) != ';' || *(p - 2) != ')') {
		fprintf(stderr, "minimoc: invalid signal at line %d\n", lineno);
		return NULL;
	}
	*(p - 1) = '\0'; // Remove ending ';'
	return buf;
}

int end_of_signals(char *buf)
{
	return *buf == '}' || !strncmp(buf, "public", 6) || !strncmp(buf, "private", 7)
		|| !strncmp(buf, "protected", 9);
}

int end_of_slots(char *buf)
{
	return *buf == '}' || !strncmp(buf, "public", 6) || !strncmp(buf, "private", 7)
		|| !strncmp(buf, "protected", 9) || !strcmp(buf, "signals");
}

void add_signal(char *signal)
{
	strcpy(signals[signals_no], signal);
	++signals_no;
}

void add_slot(char *slot)
{
	strcpy(slots[slots_no], slot);
	++slots_no;
}

void emit_class_decl(FILE *fout, char *inname)
{
	int i;
	static int header = 0;

	if (!header) {
		fprintf(fout, "// Automatically generated by the mini meta object compiler\n"
			"// from the source file \"%s\"\n"
			"// DO NOT EDIT!\n\n"
			"#include <qobject.h>\n\n"
	 		"#include \"%s\"\n\n", inname, inname);
		header = 1;
	}

	// className() function
 	fprintf(fout, "const char *%s::className() const\n{\n\treturn \"%s\";\n}\n\n",
		classname, classname);

	// initMetaObject() function
	fprintf(fout, "void %s::initMetaObject()\n{\n"
			"\tif (moc_check.wasInitialized())\n"
			"\t\treturn;\n"
			"\tmoc_check.initialize();\n",
		classname);
	for (i = 0; i < signals_no; ++i)
		fprintf(fout, "\tregisterSignal(\"%s\");\n",
		signals[i]);
	for (i = 0; i < slots_no; ++i) {
		char buf[MAX_SLOT_NAME];
		strcpy(buf, slots[i]);
		buf[strlen(slots[i]) - 2] = '\0';
		fprintf(fout, "\tregisterSlot(\"%s\", this, (QMember)&%s::%s);\n",
			slots[i], classname, buf);
	}
	fprintf(fout, "}\n\n");

	// signal functions
	for (i = 0; i < signals_no; ++i) {
		fprintf(fout, "void %s::%s\n{\n",
			classname, signals[i]);
		fprintf(fout, "\tcalledSignal(\"%s\");\n}\n\n",
			signals[i]);
	}
}

void process_file(char *inname, char *outname)
{
	FILE *fin, *fout;
	char buf[MAX_LINE_SIZE];
	int haveqobject = 0;
	int insignals = 0, inslots = 0;
	int havedef = 0;

	if ((fin = fopen(inname, "r")) == NULL) {
		fprintf(stderr, "minimoc: %s: %s\n", inname, strerror(errno));
		exit(1);
	}
	if ((fout = fopen(outname, "w")) == NULL) {
		fprintf(stderr, "minimoc: %s: %s\n", outname, strerror(errno));
		exit(1);
	}

	lineno = 0;
	while (fgets(buf, MAX_LINE_SIZE, fin) != NULL) {
		++lineno;
		eat_spaces(buf);
		if (strlen(buf) < 1)
			continue;
		if (insignals && !end_of_signals(buf)) {
			char *sig = make_signal(buf);
			if (sig != NULL)
				add_signal(sig);
		} else if (insignals) {
			insignals = 0;
		}
		if (inslots && !end_of_slots(buf)) {
			char *sig = make_signal(buf);
			if (sig != NULL)
				add_slot(sig);
		} else if (inslots) {
			inslots = 0;
		}
		if (!strncmp(buf, "class", 5)) {
			if (havedef)
				emit_class_decl(fout, inname);
			havedef = 0;
			strcpy(classname, make_classname(buf));
			haveqobject = 0;
			insignals = 0;
			inslots = 0;
			signals_no = 0;
			slots_no = 0;
		} else if (!strcmp(buf, "Q_OBJECT")) {
			haveqobject = 1;
		} else if (!strcmp(buf, "signals:")) {
			if (!haveqobject)
				fprintf(stderr, "minimoc: warning: Q_OBJECT not found in class %s\n", classname);
			havedef = 1;
			insignals = 1;
		} else if (!strcmp(buf, "public slots:")
			|| !strcmp(buf, "protected slots:")
			|| !strcmp(buf, "private slots:")) {
			if (!haveqobject)
				fprintf(stderr, "minimoc: warning: Q_OBJECT not found in class %s\n", classname);
			havedef = 1;
			inslots = 1;
		}
	}
	if (havedef)
		emit_class_decl(fout, inname);
	fclose(fin);
	fclose(fout);
}

int main(int argc, char **argv)
{
	if (argc != 3) {
		fprintf(stderr, "The Mini Meta Object Compiler\n"
	       			"usage: minimoc <infile> <outfile>\n");
		return 1;
	}
	process_file(argv[1], argv[2]);
	return 0;
}
